﻿using DotNetCommon.Extensions;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;

namespace DotNetCommon;

/// <summary>
/// Mapper帮助类，参照: <seealso cref="MapperHelper.Mapper(Type, Type, object, bool, bool)"/>
/// </summary>
public class MapperHelper
{
    /// <summary>
    /// Mapper时用的缓存字典,内部封装了 ConcurrentDictionary&lt;object, object>，由 containsRepeatReference 决定是否使用内部的字典
    /// </summary>
    public class CacheDictionary
    {
        private readonly ConcurrentDictionary<(Type from, Type dest, object obj), object> dic;
        private readonly bool containsRepeatReference = true;
        public CacheDictionary(bool containsRepeatReference, int capacity = 1024)
        {
            this.containsRepeatReference = containsRepeatReference;
            if (containsRepeatReference) dic = new ConcurrentDictionary<(Type from, Type dest, object obj), object>(1, capacity);
        }

        public bool ContainsKey((Type from, Type dest, object obj) key)
        {
            if (!containsRepeatReference) return false;
            return dic.ContainsKey(key);
        }

        public void Add((Type from, Type dest, object obj) key, object value)
        {
            if (!containsRepeatReference) return;
            dic.TryAdd(key, value);
        }

        public object get_Item((Type from, Type dest, object obj) obj)
        {
            if (!containsRepeatReference) return null;
            return dic.TryGetValue(obj, out object value) ? value : null;
        }

        public void set_Item((Type from, Type dest, object obj) key, object val)
        {
            if (!containsRepeatReference) return;
            dic.TryAdd(key, val);
        }
    }
    class Wrapper
    {
        public Func<object, CacheDictionary, bool, object[], object> Method { set; get; }
        public object Copy(object obj, CacheDictionary dic, bool null2Default, object[] args)
            => Method(obj, dic, null2Default, args);
    }

    static readonly ConcurrentDictionary<(Type from, Type dest), Wrapper> _cache = [];
    private static readonly TimeSpan timeSpan = TimeSpan.FromHours(8);
    private static readonly List<TypeCode> _baseTypes =
    [
        TypeCode.Byte,TypeCode.SByte,
        TypeCode.Int16,TypeCode.UInt16,
        TypeCode.Int32,TypeCode.UInt32,
        TypeCode.Int64,TypeCode.UInt64,
        TypeCode.Single,TypeCode.Double,TypeCode.Decimal,
        TypeCode.Char,TypeCode.Boolean,TypeCode.String,TypeCode.DateTime
    ];
    private static Func<object, CacheDictionary, bool, object[], object> _baseConvert((Type from, Type dest) cacheKey)
    {
        var typeCode = cacheKey.dest.GetTypeCode();
        var isNullAble = cacheKey.dest.IsNullable();
        if (isNullAble) typeCode = cacheKey.dest.GenericTypeArguments[0].GetTypeCode();
        var fromClassFullName = cacheKey.from.GetClassFullName();
        var destClassFullName = cacheKey.dest.GetClassFullName();
        Func<bool, object> nullFunc = (null2Default) => null2Default ?
                                (cacheKey.dest.GetDefault())
                                :
                                throw new Exception($"无法将 null or dbnull 转换到 [{destClassFullName}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destClassFullName}] 的默认值.");
        switch (typeCode)
        {
            case TypeCode.Byte:
                {
                    return wraperFunc(obj => Convert.ToByte(obj));
                }
            case TypeCode.SByte:
                {
                    return wraperFunc(obj => Convert.ToSByte(obj));
                }
            case TypeCode.Int16:
                {
                    return wraperFunc(obj => Convert.ToInt16(obj));
                }
            case TypeCode.UInt16:
                {
                    return wraperFunc(obj => Convert.ToUInt16(obj));
                }
            case TypeCode.Int32:
                {
                    return wraperFunc(obj => Convert.ToInt32(obj));
                }
            case TypeCode.UInt32:
                {
                    return wraperFunc(obj => Convert.ToUInt32(obj));
                }
            case TypeCode.Int64:
                {
                    return wraperFunc(obj => Convert.ToInt64(obj));
                }
            case TypeCode.UInt64:
                {
                    return wraperFunc(obj => Convert.ToUInt64(obj));
                }
            case TypeCode.Single:
                {
                    return wraperFunc(obj => Convert.ToSingle(obj));
                }
            case TypeCode.Double:
                {
                    return wraperFunc(obj => Convert.ToDouble(obj));
                }
            case TypeCode.Decimal:
                {
                    return wraperFunc(obj => Convert.ToDecimal(obj));
                }
            case TypeCode.Char:
                {
                    return wraperFunc(obj => Convert.ToChar(obj));
                }
            case TypeCode.Boolean:
                {
                    return wraperFunc(obj => Convert.ToBoolean(obj));
                }
            case TypeCode.String:
                {
                    return wraperFunc(obj => Convert.ToString(obj));
                }
            case TypeCode.DateTime:
                {
                    return wraperFunc(obj => Convert.ToDateTime(obj));
                }
            default:
                {
                    if (isNullAble) return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception($"无法从:[{fromClassFullName}] 转换到 [{destClassFullName}]");
                    throw new Exception($"无法从:[{fromClassFullName}] 转换到 [{destClassFullName}]");
                }
        }

        Func<object, CacheDictionary, bool, object[], object> wraperFunc(Func<object, object> actFunc)
        {
            if (isNullAble) return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : actFunc(obj);
            else return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : actFunc(obj);
        }
    }

    /// <summary>
    /// 两个poco类之间的转换,忽略父子关系,基于编译表达式实现
    /// </summary>
    /// <param name="fromType">原类型</param>
    /// <param name="destType">目标类型</param>
    /// <param name="obj">原实例</param>
    /// <param name="containsRepeatReference">是否考虑引用关系</param>
    /// <param name="null2Default">是否将null值转换为默认值,而不是抛出异常</param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="Exception"></exception>
    public static object Mapper(Type fromType, Type destType, object obj, bool containsRepeatReference = true, bool null2Default = true)
    {
        AssertUtil.NotNull(fromType);
        AssertUtil.NotNull(destType);
        //Console.WriteLine($"fromType destType IsNullable: {fromType.GetClassFullName()} => {destType.GetClassFullName()}");
        //先简单判断
        if (obj.IsNullOrDBNull())
        {
            if (!destType.IsValueType) return null;
            if (null2Default) return destType.GetDefault();
            throw new Exception($"无法将 null or dbnull 转换到 [{destType.GetClassFullName()}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destType.GetClassFullName()}] 的默认值.");
        }

        var cacheKey = (fromType, destType);
        if (_cache.ContainsKey(cacheKey))
        {
            return _cache[cacheKey].Method.Invoke(obj, new CacheDictionary(containsRepeatReference), null2Default, null);
        }
        var func = _cache.GetOrAdd(cacheKey, type =>
        {
            var tmpCache = new Dictionary<(Type from, Type dest), Wrapper>();
            var func = GetMapperMethod(cacheKey, tmpCache);
            if (tmpCache.Count > 0)
            {
                foreach (var item in tmpCache)
                {
                    _cache.TryAdd(item.Key, item.Value);
                }
            }
            return func;
        });
        return func.Method(obj, new CacheDictionary(containsRepeatReference), null2Default, null);

        #region GetMapperMethod
        Wrapper GetMapperMethod((Type from, Type dest) cacheKey, Dictionary<(Type from, Type dest), Wrapper> tmpCache)
        {
            if (_cache.TryGetValue(cacheKey, out Wrapper value)) return value;
            if (tmpCache.TryGetValue(cacheKey, out Wrapper value2)) return value2;
            var wrapper = new Wrapper();
            tmpCache.Add(cacheKey, wrapper);

            //类型相同,无需转换
            if (cacheKey.from == cacheKey.dest)
            {
                wrapper.Method = (obj, dic, null2Default, args) => obj;
            }

            var destDefault = cacheKey.dest.GetDefault();
            var fromClassFullName = cacheKey.from.GetClassFullName();
            var destClassFullName = cacheKey.dest.GetClassFullName();
            Func<bool, object> nullFunc = (null2Default) => null2Default ?
                                destDefault
                                :
                                throw new Exception($"无法将 null or dbnull 转换到 [{destClassFullName}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destClassFullName}] 的默认值.");

            #region nullable 和 非nullable互转
            //int? => int
            if (cacheKey.from.IsNullable())
            {
                if (!cacheKey.dest.IsNullable())
                {
                    if (cacheKey.from.GenericTypeArguments[0] == cacheKey.dest)
                    {
                        //int? => int
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : obj;
                        return wrapper;
                    }
                }
            }
            //int => int?
            if (!cacheKey.from.IsNullable())
            {
                if (cacheKey.dest.IsNullable())
                {
                    if (cacheKey.dest.GenericTypeArguments[0] == cacheKey.from)
                    {
                        //int => int?
                        wrapper.Method = (obj, dic, null2Default, args) => obj;
                        return wrapper;
                    }
                }
            }
            #endregion

            //基础类型
            var from = cacheKey.from;
            if (cacheKey.from.IsNullable()) from = cacheKey.from.GenericTypeArguments[0];
            var dest = cacheKey.dest;
            if (cacheKey.dest.IsNullable()) dest = cacheKey.dest.GenericTypeArguments[0];

            #region 其他 => string
            if (cacheKey.dest == typeof(string))
            {
                //DateTime? => string 或 DateTime => string
                if (from == typeof(DateTime))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                        (obj.IsNullOrDBNull() ? null : ((DateTime?)obj)?.ToString())
                        : ((DateTime?)obj)?.ToString(args.FirstOrDefault().ToString());
                    return wrapper;
                }
                //DateTimeOffset? => string 或 DateTimeOffset => string
                if (from == typeof(DateTimeOffset))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                        (obj.IsNullOrDBNull() ? null : ((DateTimeOffset?)obj)?.ToString())
                        : ((DateTimeOffset?)obj)?.ToString(args.FirstOrDefault().ToString());
                    return wrapper;
                }
                //DateOnly? => string 或 DateOnly => string
                if (from == typeof(DateOnly))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                        (obj.IsNullOrDBNull() ? null : ((DateOnly?)obj)?.ToString())
                        : ((DateOnly?)obj)?.ToString(args.FirstOrDefault().ToString());
                    return wrapper;
                }
                //TimeOnly? => string 或 TimeOnly => string
                if (from == typeof(TimeOnly))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                        (obj.IsNullOrDBNull() ? null : ((TimeOnly?)obj)?.ToString())
                        : ((TimeOnly?)obj)?.ToString(args.FirstOrDefault().ToString());
                    return wrapper;
                }
                //Guid? => string 或 Guid => string
                if (from == typeof(Guid))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                        (obj.IsNullOrDBNull() ? null : ((Guid?)obj).ToString())
                        : ((Guid?)obj)?.ToString(args.FirstOrDefault().ToString());
                    return wrapper;
                }
                wrapper.Method = (obj, dic, null2Default, args) => obj?.ToString();
                return wrapper;
            }
            #endregion
            #region 其他 => ValueType
            if (dest.IsValueType)
            {
                #region 其他 => enum
                if (dest.IsEnum)
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : Enum.Parse(dest, obj?.ToString(), true);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : Enum.Parse(dest, obj?.ToString(), true);
                    }
                    return wrapper;
                }
                #endregion
                #region 字符串转日期
                //string => DateTime 或 string => DateTime?
                if (from == typeof(string) && dest == typeof(DateTime))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            null
                            : (args.IsNullOrEmpty() ?
                                DateTime.Parse(obj.ToString())
                                : DateTime.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            nullFunc(null2Default)
                            : (args.IsNullOrEmpty() ?
                                DateTime.Parse(obj.ToString())
                                : DateTime.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    return wrapper;
                }
                //string => DateTimeOffset 或 string => DateTimeOffset?
                if (from == typeof(string) && dest == typeof(DateTimeOffset))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            null
                            : (args.IsNullOrEmpty() ?
                                DateTimeOffset.Parse(obj.ToString())
                                : DateTimeOffset.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            nullFunc(null2Default)
                            : (args.IsNullOrEmpty() ?
                                DateTimeOffset.Parse(obj.ToString())
                                : DateTimeOffset.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    return wrapper;
                }
                //string => DateOnly
                if (from == typeof(string) && dest == typeof(DateOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            null :
                            (args.IsNullOrEmpty() ?
                                DateOnly.Parse(obj.ToString())
                                : DateOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            nullFunc(null2Default)
                            : (args.IsNullOrEmpty() ?
                                DateOnly.Parse(obj.ToString())
                                : DateOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    return wrapper;
                }
                //string => TimeOnly
                if (from == typeof(string) && dest == typeof(TimeOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            null
                            : (args.IsNullOrEmpty() ?
                                TimeOnly.Parse(obj.ToString())
                                : TimeOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                            nullFunc(null2Default)
                            : (args.IsNullOrEmpty() ?
                                TimeOnly.Parse(obj.ToString())
                                : TimeOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                    }
                    return wrapper;
                }
                #endregion
                #region DateTime/DateTimeOffset/TimeSpan => DateOnly/TimeOnly
                //DateTime => DateOnly
                if (from == typeof(DateTime) && dest == typeof(DateOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : DateOnly.FromDateTime((DateTime)obj);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : DateOnly.FromDateTime((DateTime)obj);
                    }
                    return wrapper;
                }
                //DateTimeOffset => DateOnly
                if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : DateOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : DateOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                    }
                    return wrapper;
                }
                //TimeSpan => DateOnly
                if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception("无法从TimeSpan转到DateOnly!");
                    }
                    else
                    {
                        throw new Exception("无法从TimeSpan转到DateOnly!");
                    }
                    return wrapper;
                }
                //DateTime => TimeOnly
                if (from == typeof(DateTime) && dest == typeof(TimeOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromDateTime((DateTime)obj);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromDateTime((DateTime)obj);
                    }
                    return wrapper;
                }
                //DateTimeOffset => TimeOnly
                if (from == typeof(DateTimeOffset) && dest == typeof(TimeOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                    }
                    return wrapper;
                }
                //TimeSpan => TimeOnly
                if (from == typeof(TimeSpan) && dest == typeof(TimeOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromTimeSpan((TimeSpan)obj);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromTimeSpan((TimeSpan)obj);
                    }
                    return wrapper;
                }
                #endregion
                #region DateOnly/TimeOnly => DateTime/DateTimeOffset/TimeSpan
                //DateOnly => DateTime
                if (from == typeof(DateOnly) && dest == typeof(DateTime))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day);
                    }
                    return wrapper;
                }
                //DateOnly => DateTimeOffset
                if (from == typeof(DateOnly) && dest == typeof(DateTimeOffset))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day, 0, 0, 0, timeSpan);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day, 0, 0, 0, timeSpan);
                    }
                    return wrapper;
                }
                //DateOnly => TimeSpan
                if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception("无法从DateOnly转到TimeSpan!");
                    }
                    else
                    {
                        throw new Exception("无法从DateOnly转到TimeSpan!");
                    }
                    return wrapper;
                }
                //TimeOnly => DateTime
                if (from == typeof(TimeOnly) && dest == typeof(DateTime))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                    }
                    return wrapper;
                }
                //TimeOnly => DateTimeOffset
                if (from == typeof(TimeOnly) && dest == typeof(DateTimeOffset))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond, timeSpan);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond, timeSpan);
                    }
                    return wrapper;
                }
                //TimeOnly => TimeSpan
                if (from == typeof(TimeOnly) && dest == typeof(TimeSpan))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new TimeSpan(0, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new TimeSpan(0, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                    }
                    return wrapper;
                }
                #endregion
                #region DateTimeOffset 转 DateTime
                if (from == typeof(DateTimeOffset) && dest == typeof(DateTime))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : ((DateTimeOffset)obj).DateTime;
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : ((DateTimeOffset)obj).DateTime;
                    }
                    return wrapper;
                }
                #endregion
                #region DateTime 转 DateTimeOffset
                if (from == typeof(DateTime) && dest == typeof(DateTimeOffset))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset((DateTime)obj);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset((DateTime)obj);
                    }
                    return wrapper;
                }
                #endregion
                #region TimeSpan 转 DateTime DateTimeOffset
                if (from == typeof(TimeSpan) && dest == typeof(DateTime))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds);
                    }
                    return wrapper;
                }
                if (from == typeof(TimeSpan) && dest == typeof(DateTimeOffset))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds, timeSpan);
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds, timeSpan);
                    }
                    return wrapper;
                }
                #endregion
                #region 字符串转bool
                if (from == typeof(string) && dest == typeof(bool))
                {
                    var arr = new string[] { "OK", "YES", "TRUE", "1", "是" };
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : arr.Contains(obj.ToString());
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : arr.Any(i => string.Equals(i, obj?.ToString(), StringComparison.OrdinalIgnoreCase));
                    }
                    return wrapper;
                }
                #endregion
                #region 字符串转guid
                if (from == typeof(string) && dest == typeof(Guid))
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : Guid.Parse(obj.ToString());
                    }
                    else
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : Guid.Parse(obj.ToString());
                    }
                    return wrapper;
                }
                #endregion
                #region 可以使用Convert.ToXXX的转换
                var typeCode = dest.GetTypeCode();
                if (_baseTypes.Contains(typeCode))
                {
                    wrapper.Method = _baseConvert(cacheKey);
                    return wrapper;
                }
                #endregion
                #region 其他 使用 JsonConvert 实现
                if (cacheKey.dest.IsNullable())
                {
                    //Console.WriteLine($"JsonConvert IsNullable: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}");
                    wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonSerializer.Deserialize(JsonSerializer.Serialize(obj), cacheKey.dest);
                }
                else
                {
                    //Console.WriteLine($"JsonConvert: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}");
                    wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : JsonSerializer.Deserialize(JsonSerializer.Serialize(obj), cacheKey.dest);
                }
                return wrapper;
                #endregion
            }
            #endregion
            #region json字符串反序列化
            if (cacheKey.from == typeof(string) && cacheKey.dest.IsClass)
            {
                wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonSerializer.Deserialize(obj.ToString(), cacheKey.dest);
                return wrapper;
            }
            #endregion

            var destReflect = dest.GetClassGenericFullName();
            var fromReflect = from.GetClassGenericFullName();

            #region => List<T>
            if (destReflect.Name == "System.Collections.Generic.List<T>" || destReflect.Name == "System.Collections.Generic.IList<T>" || destReflect.Name == "System.Collections.Generic.ICollection<T>")
            {
                //IEnumerable<T> => List<T>
                //Array<T> => List<T>
                if (from.IsAssignableTo(typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type)))
                {
                    GetMapperMethod_IEnumerable_List();
                    return wrapper;
                }
                //IEnumerable<T> => List<T2>
                //Array<T> => List<T2>
                Type enumerable = null;
                if (from.IsInterface && from.GetClassFullName().StartsWith("System.Collections.Generic.IEnumerable<"))
                {
                    enumerable = from;
                }
                else
                {
                    enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                }
                if (enumerable != null)
                {
                    GetMapperMethod_IEnumerable_List2(enumerable.GetClassGenericFullName().GenericTypes.First().type, destReflect.GenericTypes.First().type);
                    return wrapper;
                }
                throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
            }
            #endregion
            #region => IEnumerable<T>
            if (destReflect.Name == "System.Collections.Generic.IEnumerable<T>")
            {
                //{IEnumerable<T>} => IEnumerable<T>
                if (from.IsAssignableTo(typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type)))
                {
                    wrapper.Method = (obj, dic, null2Default, args) => obj;
                    return wrapper;
                }
                //{IEnumerable<T>} => IEnumerable<T2>
                Type enumerable = null;
                if (from.IsInterface && from.GetClassFullName().StartsWith("System.Collections.Generic.IEnumerable<"))
                {
                    enumerable = from;
                }
                else
                {
                    enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                }
                if (enumerable != null)
                {
                    GetMapperMethod_IEnumerable_List2(enumerable.GetClassGenericFullName().GenericTypes.First().type, destReflect.GenericTypes.First().type);
                    return wrapper;
                }
                throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
            }
            #endregion
            #region => Array<T>
            if (dest.IsArray)
            {
                if (from.IsAssignableTo(typeof(IEnumerable<>).MakeGenericType(dest.GetElementType())))
                {
                    //{IEnumerable<T>} => Array<T>
                    var method = typeof(Enumerable).GetMethod("ToArray");
                    Func<object, object> func = obj => method.Invoke(null, new object[] { obj });
                    wrapper.Method = (obj, dic, null2Default, args) => obj == null ? null :
                        (dic.ContainsKey((from, dest, obj)) ? dic.get_Item((from, dest, obj)) : func(obj));
                    return wrapper;
                }
                var enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                if (enumerable != null)
                {
                    //{IEnumerable<T>} => Array<T2>
                    GetMapperMethod_IEnumerable_Array2(enumerable.GetClassGenericFullName().GenericTypes.First().type, dest.GetElementType());
                    return wrapper;
                }
                throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
            }
            #endregion
            GetMapperMethod_Poco();
            return wrapper;

            #region poco
            void GetMapperMethod_Poco()
            {
                var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp(cacheKey);
                //先获取所有构造函数
                var ctors = dest.GetConstructors();
                //优先使用空参构造函数, 无论是否是 public
                var ctor = ctors.FirstOrDefault(i => i.GetParameters().Length == 0);
                ParameterInfo[] ctorParameters = null;
                if (ctor == null)
                {
                    ctors = ctors.Where(i => i.IsPublic).ToArray();
                    if (ctors.Length != 1)
                    {
                        //只能有一个 否则失败
                        throw new NotSupportedException($"类: {dest.GetClassFullName()} 没有空参构造函数, 也没有找到适合的public构造函数, 失败!");
                    }
                    ctor = ctors.First();
                    ctorParameters = ctor.GetParameters();
                }

                var destProps = dest.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
                var fromProps = from.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                var destFields = dest.GetFields(BindingFlags.Public | BindingFlags.Instance).ToList();
                var fromFields = from.GetFields(BindingFlags.Public | BindingFlags.Instance);

                //属性间Mapper的可能定义参数的类型
                var argTypes = new[] { typeof(DateTime), typeof(DateTime?), typeof(DateTimeOffset), typeof(DateTimeOffset?), typeof(Guid), typeof(Guid?) };
                var getFormatterPara = (MemberInfo fromMember, MemberInfo destMember) =>
                {
                    var formatter = "";
                    var converter = fromMember?.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                    if (converter == null)
                    {
                        converter = destMember?.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                    }
                    if (converter != null && converter.Args?.Length > 0)
                    {
                        formatter = converter.Args.FirstOrDefault().ToString();
                    }
                    return formatter;
                };

                var localRes = Expression.Variable(dest, "localRes");
                var localObj = Expression.Variable(from, "localObj");
                var assignLocalObj = Expression.Assign(localObj, from.IsClass ? Expression.TypeAs(para_obj, from) : Expression.Convert(para_obj, from));

                //先处理构造函数
                BinaryExpression assignRes = null;//构造函数赋值 res=new Person() or res=new Person(id,name)
                if (ctorParameters == null)
                {
                    //使用空参构造函数
                    assignRes = Expression.Assign(localRes, Expression.New(ctor));//res=new Person()
                }
                else
                {
                    //使用带参构造函数
                    var paras = new List<Expression>(ctorParameters.Length);
                    for (int pidx = 0; pidx < ctorParameters.Length; pidx++)
                    {
                        var item = ctorParameters[pidx];
                        //从目标上找对应的属性, 如(A -> B): A { prop C} B {ctor(c)  prop C}
                        //虽然看起来从哪找都行, 但从B上找到属性, 再据此找到A上的属性更合理
                        MemberInfo destMember = (MemberInfo)destProps.FirstOrDefault(i => string.Equals(i.Name, item.Name, StringComparison.OrdinalIgnoreCase)) ?? fromFields.FirstOrDefault(i => string.Equals(i.Name, item.Name, StringComparison.OrdinalIgnoreCase));
                        if (destMember == null)
                        {
                            //没有找到 用参数类型的默认值
                            paras.Add(Expression.Constant(item.ParameterType.GetDefault(), item.ParameterType));
                            continue;
                        }
                        //找到了 B 上的属性, 找 A 上的对应属性
                        MemberInfo fromMember = (MemberInfo)fromProps.FirstOrDefault(i => i.Name == destMember.Name) ?? fromFields.FirstOrDefault(i => i.Name == destMember.Name);
                        if (fromMember == null)
                        {
                            //没有找到 用参数类型的默认值
                            paras.Add(Expression.Constant(item.ParameterType.GetDefault(), item.ParameterType));
                            continue;
                        }
                        var fromMemberType = (fromMember is PropertyInfo propertyInfo) ? propertyInfo.PropertyType : ((FieldInfo)fromMember).FieldType;
                        Expression getVal = null;

                        if (fromMemberType == item.ParameterType)
                        {
                            //ctorB(A.prop)
                            getVal = Expression.MakeMemberAccess(localObj, fromMember);
                        }
                        else
                        {
                            //先获取 Mapper 方法
                            var innerMapper = GetMapperMethod((fromMemberType, item.ParameterType), tmpCache);
                            //获取 Mapper 中可能用到的参数
                            var formatterPara = getFormatterPara(fromMember, null);

                            if (formatterPara.IsNotNullOrEmpty())
                            {
                                getVal = Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), [Expression.Convert(Expression.MakeMemberAccess(localObj, fromMember), typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatterPara }, typeof(object[]))]), item.ParameterType);
                            }
                            else
                            {
                                getVal = Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), [Expression.Convert(Expression.MakeMemberAccess(localObj, fromMember), typeof(object)), para_dic, para_null2Default, para_args]), item.ParameterType);
                            }
                        }
                        paras.Add(getVal);
                        if (fromMember is PropertyInfo propertyInfo1) destProps.Remove(propertyInfo1);
                        else if (fromMember is FieldInfo fieldInfo) destFields.Remove(fieldInfo);
                    }
                    assignRes = Expression.Assign(localRes, Expression.New(ctor, paras));//res=new Person(id,name)
                }
                destProps = destProps.Where(i => i.CanWrite).ToList();

                var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                var valueCtor = valueType.GetConstructor([typeof(Type), typeof(Type), typeof(object)]);
                var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), [
                    Expression.New(valueCtor, Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj),
                localRes]);

                #region 公共属性 or 公共字段
                var assigns = new List<BinaryExpression>();

                var destMembers = new List<MemberInfo>();
                destMembers.AddRange(destProps);
                destMembers.AddRange(destFields);

                for (int i = 0; i < destMembers.Count; i++)
                {
                    var destMember = destMembers[i];
                    var destMemberType = destMember.MemberType == MemberTypes.Property ? (destMember as PropertyInfo).PropertyType : (destMember as FieldInfo).FieldType;
                    var destMemberExp = destMember.MemberType == MemberTypes.Property ? Expression.Property(localRes, destMember.Name) : Expression.Field(localRes, destMember.Name);

                    var fromMember = fromProps.FirstOrDefault(i => i.Name == destMember.Name) as MemberInfo;
                    fromMember ??= fromFields.FirstOrDefault(i => i.Name == destMember.Name);
                    fromMember ??= fromProps.FirstOrDefault(i => string.Equals(i.Name, destMember.Name, StringComparison.OrdinalIgnoreCase));
                    fromMember ??= fromFields.FirstOrDefault(i => string.Equals(i.Name, destMember.Name, StringComparison.OrdinalIgnoreCase));

                    if (fromMember == null) continue;

                    var fromMemberType = fromMember.MemberType == MemberTypes.Property ? (fromMember as PropertyInfo).PropertyType : (fromMember as FieldInfo).FieldType;
                    var fromMemberExp = fromMember.MemberType == MemberTypes.Property ? Expression.Property(localObj, fromMember.Name) : Expression.Field(localObj, fromMember.Name);

                    if (fromMemberType == destMemberType)
                    {
                        assigns.Add(Expression.Assign(destMemberExp, fromMemberExp));
                    }
                    else
                    {
                        //先获取 Mapper 方法
                        var innerMapper = GetMapperMethod((fromMemberType, destMemberType), tmpCache);
                        //获取 Mapper 中可能用到的参数
                        var formatterPara = getFormatterPara(fromMember, destMember);
                        if (formatterPara.IsNotNullOrEmpty())
                        {
                            assigns.Add(Expression.Assign(destMemberExp, Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), [Expression.Convert(fromMemberExp, typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatterPara }, typeof(object[]))]), destMemberType)));
                        }
                        else
                        {
                            assigns.Add(Expression.Assign(destMemberExp, Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), [Expression.Convert(fromMemberExp, typeof(object)), para_dic, para_null2Default, para_args]), destMemberType)));
                        }
                    }
                }
                #endregion

                var blocks = new List<Expression> {
                    ifnullExp,
                    //调试
                    //Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant($"haha2:{cacheKey.from.GetClassFullName()}=>{cacheKey.dest.GetClassFullName()}")),
                    ifCacheExp,
                    assignLocalObj,
                    assignRes,
                    addDic };
                blocks.AddRange(assigns);
                blocks.AddRange([Expression.Goto(retLabel, localRes), retExp]);
                var block = Expression.Block([localObj, localRes], blocks);
                var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                wrapper.Method = finalExp.Compile();
            }
            #endregion
            #region IEnumerable<T> => List<T>
            void GetMapperMethod_IEnumerable_List()
            {
                var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp(cacheKey);
                var ienumtype = typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type);
                var localRes = Expression.Variable(dest, "localRes");
                var localObj = Expression.Variable(ienumtype, "localObj");
                var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, ienumtype));
                var assignRes = Expression.Assign(localRes, Expression.Call(null, typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(destReflect.GenericTypes.First().type), localObj));

                var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                    Expression.New(valueCtor, Expression.Constant(fromType), Expression.Constant(destType), para_obj),
                localRes});

                var blocks = new List<Expression> { ifnullExp,
                    ifCacheExp,
                    assignLocalObj,
                    assignRes,
                    addDic,
                    Expression.Goto(retLabel, localRes),
                    retExp
                };
                var block = Expression.Block(new ParameterExpression[] { localObj, localRes }, blocks);
                var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                wrapper.Method = finalExp.Compile();
            }
            #endregion
            #region IEnumerable<T> => List<T2> || IEnumerable<T2>
            void GetMapperMethod_IEnumerable_List2(Type fromElement, Type destElement)
            {
                var newFromType = typeof(IEnumerable<>).MakeGenericType(fromElement);
                var newDestType = typeof(List<>).MakeGenericType(destElement);

                var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp((newFromType, newDestType));
                var localRes = Expression.Variable(newDestType, "localRes");
                var localObj = Expression.Variable(newFromType, "localObj");
                var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, newFromType));

                var ratorType = typeof(IEnumerator<>).MakeGenericType(fromElement);
                //IEnumerator<Person> rator;
                var ratorVar = Expression.Variable(ratorType, "rator");
                //rator=obj2.GetEnumerator();
                var assignRator = Expression.Assign(ratorVar, Expression.Call(localObj, newFromType.GetMethod("GetEnumerator")));

                var newListExp = Expression.New(newDestType.GetConstructor(new Type[0]));
                //res=new List<PersonDto>()
                var assignRes = Expression.Assign(localRes, newListExp);
                var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                    Expression.New(valueCtor, Expression.Constant(newFromType), Expression.Constant(newDestType), para_obj),
                localRes});

                var breakLabel = Expression.Label("break");

                var innerMapper = GetMapperMethod((fromElement, destElement), tmpCache);

                var moveNext = typeof(IEnumerator).GetMethod("MoveNext");

                //while (rator.MoveNext())
                var loopBody = Expression.Block(
                    Expression.IfThenElse(Expression.IsTrue(Expression.Call(ratorVar, moveNext)),
                        Expression.Block(
                            Expression.Call(//res.Add(innerMapper(rator.Current))
                localRes
                                , newDestType.GetMethod("Add", new Type[] { destElement })
                                , Expression.Convert(Expression.Invoke(//innerMapper(rator.Current)
                                        Expression.MakeMemberAccess(Expression.Constant(innerMapper), typeof(Wrapper).GetMember("Method")[0])//wrapper.Method.Invoke()
                                        , Expression.Convert(Expression.Property(ratorVar, "Current"), typeof(object))//(object)rator.Current
                                        , para_dic
                                        , para_null2Default
                                        , para_args
                                    ), destElement)
                            )
                        )
                        , Expression.Goto(breakLabel))
                );
                var loopExp = Expression.Loop(loopBody, breakLabel);
                var block = Expression.Block(
                    new ParameterExpression[] { localObj, localRes, ratorVar },
                    //调试
                    //Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("haha")),
                    ifnullExp,
                    ifCacheExp,
                    assignLocalObj,
                    assignRator,
                    assignRes,
                    addDic,
                    loopExp,
                    Expression.Goto(retLabel, localRes),
                    retExp
                    );
                var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                wrapper.Method = finalExp.Compile();
            }
            #endregion
            #region IEnumerable<T> => Array<T2>
            void GetMapperMethod_IEnumerable_Array2(Type fromElement, Type destElement)
            {
                var newFromType = typeof(IEnumerable<>).MakeGenericType(fromElement);
                var newDestType = destElement.MakeArrayType();

                var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp((newFromType, newDestType));
                var localRes = Expression.Variable(newDestType, "localRes");
                var localObj = Expression.Variable(newFromType, "localObj");
                var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, newFromType));

                var countMethod = typeof(Enumerable).GetMethods().Where(i => i.Name == "Count").FirstOrDefault(i => i.GetParameters().Length == 1).MakeGenericMethod(fromElement);
                var localCount = Expression.Variable(typeof(int), "count");
                var assignCount = Expression.Assign(localCount, Expression.Call(null, countMethod, new Expression[] { localObj }));

                var ratorType = typeof(IEnumerator<>).MakeGenericType(fromElement);
                //IEnumerator<Person> rator;
                var ratorVar = Expression.Variable(ratorType, "rator");
                //rator=obj2.GetEnumerator();
                var assignRator = Expression.Assign(ratorVar, Expression.Call(localObj, newFromType.GetMethod("GetEnumerator")));

                var newArrayExp = Expression.NewArrayBounds(destElement, localCount);
                //res=new PersonDto[](count)
                var assignRes = Expression.Assign(localRes, newArrayExp);
                var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                    Expression.New(valueCtor, Expression.Constant(newFromType), Expression.Constant(newDestType), para_obj),
                localRes});

                var breakLabel = Expression.Label("break");

                var innerMapper = GetMapperMethod((fromElement, destElement), tmpCache);

                //for(var i=0;i<count;i++)
                //int i;
                var localVar_i = Expression.Variable(typeof(int), "i");
                //i=0;
                var loopInit = Expression.Assign(localVar_i, Expression.Constant(0));//i=0

                var moveNext = typeof(IEnumerator).GetMethod("MoveNext");

                //while (rator.MoveNext())
                var loopBody = Expression.Block(
                    Expression.IfThenElse(Expression.IsTrue(Expression.Call(ratorVar, moveNext)),
                        Expression.Block(
                            Expression.Assign(Expression.ArrayAccess(localRes, localVar_i),
                               Expression.Convert(Expression.Invoke(//innerMapper(obj2[i])
                                   Expression.MakeMemberAccess(Expression.Constant(innerMapper), typeof(Wrapper).GetMember("Method")[0])//wrapper.Method.Invoke()
                                    , Expression.Convert(Expression.Property(ratorVar, "Current"), typeof(object))//(object)rator.Current
                                    , para_dic
                                    , para_null2Default
                                    , para_args
                                ), destElement)
                            ),
                            Expression.PostIncrementAssign(localVar_i)//i++;
                        )
                        , Expression.Goto(breakLabel))
                );
                var loopExp = Expression.Loop(loopBody, breakLabel);
                var block = Expression.Block(
                    new ParameterExpression[] { localObj, localRes, ratorVar, localCount, localVar_i },
                    ifnullExp,
                    ifCacheExp,
                    assignLocalObj,
                    assignCount,
                    assignRator,
                    assignRes,
                    addDic,
                    loopInit,
                    loopExp,
                    Expression.Goto(retLabel, localRes),
                    retExp
                    );
                var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                wrapper.Method = finalExp.Compile();
            }
            #endregion
            #region GetCommonExp
            (ParameterExpression para_obj,
                            ParameterExpression para_dic,
                            ParameterExpression para_null2Default,
                            ParameterExpression para_args,
                            LabelTarget retLabel,
                            LabelExpression retExp,
                            ConditionalExpression ifnullExp,
                            ConditionalExpression ifCacheExp
                            ) GetCommonExp((Type from, Type dest) cacheKey)
            {
                var para_obj = Expression.Parameter(typeof(object), "obj");//(object obj)
                var para_dic = Expression.Parameter(typeof(CacheDictionary), "dic");//(dictionary<object,object> dic)
                var para_null2Default = Expression.Parameter(typeof(bool), "null2Default");//(bool null2Default)
                var para_args = Expression.Parameter(typeof(object[]), "args");//(object[] args)
                var retLabel = Expression.Label(typeof(object), "ret");
                var retExp = Expression.Label(retLabel, para_obj);
                var ifnullExp = Expression.IfThen(Expression.Equal(para_obj, Expression.Constant(null)), Expression.Return(retLabel, para_obj));//if (i==null) return i;

                var type = typeof(ValueTuple<,,>).MakeGenericType([typeof(Type), typeof(Type), typeof(object)]);
                var ctor = type.GetConstructor([typeof(Type), typeof(Type), typeof(object)]);
                //if (dic.ContainsKey((cacheKey.from,cacheKey.dest,obj))) return dic[dic.ContainsKey((cacheKey.from,cacheKey.dest,obj))];
                var ifCacheExp = Expression.IfThen(
                    Expression.IsTrue(
                        Expression.Call(
                            para_dic,
                            typeof(CacheDictionary).GetMethod("ContainsKey"),
                            [Expression.New(ctor, [Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj])]
                        )
                    ),
                    Expression.Return(retLabel, Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("get_Item"), Expression.New(ctor, [Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj]))));
                return (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp);
            }
            #endregion

            #endregion
        }
    }

    /// <summary>
    /// 注册指定 fromType => destType 的Mapper逻辑，如:
    /// <code>
    /// MapperHelper.RegisterMapperHander((typeof(Person), typeof(PersonDto)), (obj, dic, containsRepeatReference, null2Default) =>
    /// {
    ///     var key = (typeof(Person), typeof(PersonDto), obj);
    ///     if (dic.ContainsKey(key)) return dic.get_Item(key);
    ///     var res = new PersonDto();
    ///     dic.set_Item(key, res);
    ///     var person = obj as Person;
    ///     res.Id = person.Id + 1;
    ///     res.Name = person.Name + "Mapper";
    ///     return res;
    /// });
    /// var pers = new Person
    /// {
    ///     Id = 1,
    ///     Name = "小明"
    /// };
    /// var dto = pers.Mapper&lt;PersonDto>();
    /// dto.Id.ShouldBe(2);
    /// dto.Name.ShouldBe("小明Mapper");
    /// </code>
    /// </summary>
    /// <param name="type"></param>
    /// <param name="func"></param>
    public static void RegisterMapperHander((Type fromType, Type destType) type, Func<object, CacheDictionary, bool, object[], object> func)
    {
        AssertUtil.NotNull(type.fromType);
        AssertUtil.NotNull(type.destType);
        AssertUtil.NotNull(func);
        _cache.TryAdd(type, new Wrapper
        {
            Method = func
        });
    }
}
